`include "defines.v"
module mdu(
  input               start ,
  input [`OP_W-1:0]   mode  ,
  input [`XLEN-1:0]   op1   , 
  input [`XLEN-1:0]   op2   ,
  output              done  ,
  output[`XLEN-1:0]   out     
);

wire [`XLEN-1:0] op1Invert = op1[`XLEN-1] ? ~op1 + 1'b1 : op1;
wire [`XLEN-1:0] op2Invert = op2[`XLEN-1] ? ~op2 + 1'b1 : op2;

wire [`XLEN-1:0] mulOp1 = ((mode ==  `MDU_MUL ) || (mode == `MDU_MULH) || (mode == `MDU_MULW))? op1 : op1Invert;
wire [`XLEN-1:0] mulOp2 = ((mode ==  `MDU_MUL ) || (mode == `MDU_MULH) || (mode == `MDU_MULHSU)|| (mode == `MDU_MULW)) ? op2 : op2Invert;
wire [`XLEN*2-1:0] mulTemp       = mulOp1 * mulOp2;
wire [`XLEN*2-1:0] mulTempInvert = ~mulTemp + 1'b1;

reg [`XLEN-1:0] mulOut;
always @(*) begin
  if(mode == `MDU_MUL)
    mulOut = mulTemp[63:0];
  else if(mode == `MDU_MULHU)
    mulOut = mulTemp[127:64];
  else if(mode == `MDU_MULH)begin
    case ({op1[63], op2[63]})
        2'b00:mulOut = mulTemp[127:64];
        2'b11:mulOut = mulTemp[127:64];  
        2'b10:mulOut = mulTempInvert[127:64];
        default:mulOut = mulTempInvert[127:64];
    endcase
  end else if(mode == `MDU_MULHSU)
    mulOut = op1[63] ? mulTempInvert[127:64] : mulTemp[127:64];
  else if(mode == `MDU_MULW)
    mulOut = {{32{mulTemp[31]}},mulTemp[31:0]};
end
reg [`XLEN-1:0] divOut;

always@(*)begin
  if(mode == `DIV)         divOut = $signed(op1) / $signed(op2);
  else if(mode == `DIVU  ) divOut = op1 / op2;
  else if(mode == `DIVW  ) divOut = $signed(op1[31:0]) / $signed(op2[31:0]);
  else if(mode == `DIVUW ) divOut = op1[31:0] / op2[31:0];
  else if(mode == `REM   ) divOut = $signed(op1) % $signed(op2);
  else if(mode == `REMU  ) divOut = op1 % op2;
  else if(mode == `REMW  ) divOut = $signed(op1[31:0]) % $signed(op2[31:0]);
  else if(mode == `REMUW ) divOut = op1[31:0] % op2[31:0];
end

assign done = start; 
assign out  = mode[`OP_W-1] ? divOut : mulOut; 
endmodule